Fix Oracle durability operation batch execution - #3615
Conversation
|
@pedroandrade03 This isn't going to make the release today, but will by EOD Monday |
|
Thanks |
Review notesRead this one closely since it touches the shared RDBMS durability path. The root cause is right — A few things to work through. 1. Test coverage is the main gapThe reported failure in #3614 was The good news is this is cheap: 2. Parity divergences from the generic pathWorth deciding on each deliberately rather than by omission:
3. The string-level SQL surgery is the fragile part
Minimum: state the assumption in a comment on both helpers. Better long-term option worth considering — the operations hardcode the marker themselves ( Related: the "returns data and must contain exactly one SQL statement" 4. Naming and placement
5. One question
CIGreen across the full matrix on this branch, 🤖 Review assisted by Claude Code |
|
Follow-up to my review notes above, consolidating what needs to happen here so this doesn't have to be reconstructed later. Where this stands. The diagnosis is correct and the shape of the fix is right — per-operation command, one Oracle transaction, opt-in hook so no other provider changes behavior. CI is green including The one thing I'd most like before merge: an integration test in The rest, in priority order:
Worth considering, separately from this PR. The rewrite exists because the shared operations hardcode the marker into the SQL text ( @pedroandrade03 — thanks for the thorough write-up on the original PR, and for tracking this down past #3589. The 🤖 Review assisted by Claude Code |
|
Thanks for the thorough root-cause work here, @pedroandrade03 — the diagnosis in this PR is what made the fix possible, and your test cases are carried forward in #3659 with you as co-author. We went a different route on the implementation. Rather than give Oracle a provider-specific Two of the three things this PR fixed turned out to already exist in Weasel and just weren't wired up: the Working through it surfaced two cases the
Your PR also indirectly turned up a latent Weasel bug: the Guid→RAW conversion was a Closing this in favour of #3659. Really appreciate the contribution. |
#3659) * fix(oracle): run the durability agent through the shared batching mechanics The durability agent batches its whole recovery operation set into one command builder and executes it. Oracle's message store handed back the generic DbCommandBuilder, which emits `@` bind markers and concatenates every statement into a single command. ODP.NET rejects both -- it has no DbBatch support at all (CanCreateBatch is false, CreateBatch throws) and will not execute several statements from one command -- so the agent threw ORA-00933 / ORA-00936 / ORA-03405 on every sweep and nothing persisted in the inbox or outbox was ever recovered. Rather than give Oracle a bespoke execution path, this teaches the shared batching mechanics about statement boundaries and lets the provider decide what they mean. DatabaseOperationBatch now marks a boundary before each operation and executes whatever CompileCommands() hands back. On every provider whose driver can execute several statements from one command, StartNewCommand() is a no-op, CompileCommands() returns a single command, and the behaviour is byte for byte what it was. Oracle returns Weasel.Oracle's OracleDbCommandBuilder, which emits `:` markers, types parameters through OracleProvider, and splits. Three things the semicolon-splitting approach would have missed: - Four operations write more than one statement each (both ReleaseOrphaned variants, MoveReplayableErrorMessagesToIncoming, and PersistNodeRecord's insert per event). Splitting per operation is not enough, so those now mark their internal boundaries explicitly. - MoveReplayableErrorMessagesToIncoming binds :replayable from two different statements. AddNamedParameter finds-or-adds, so it exists once and has to be bound to both split commands. - The same operation hard-coded `@replayable` in its SQL text, which no provider-neutral consumer should do. It reads the marker off the builder now. Also documents the real reason OracleMessageStore.EnqueueAsync is a no-op: it implements IMessageDatabase directly rather than deriving from MessageDatabase, so it has no DatabaseBatcher. The durability agent does not use that path. Adds Pedro Andrade's coverage from #3615, retargeted at the new design and extended with an end-to-end assertion that the real recovery batch runs against a real Oracle database -- red-verified as ORA-03405 before this change. Fixes #3614. Co-Authored-By: Pedro Henrique Andrade Siqueira <pedroandrade03@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(deps): Weasel 9.19.0 Brings in JasperFx/weasel#390 -- Weasel.Oracle's OracleDbCommandBuilder and the StartNewCommand()/CompileCommands() statement-boundary hooks on CommandBuilderBase that the Oracle durability fix is built on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Pedro Henrique Andrade Siqueira <pedroandrade03@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
@namebind markers to Oracle:namemarkers and normalize boolean andGuidparameter valuesBackground
#3589 corrected the Oracle message store agent URI, which allows the durability agent to start. Once running, the agent exposed a second Oracle-specific failure: generic RDBMS durability batches concatenate multiple SQL statements into one command and use
@bind markers. ODP.NET rejects those commands withORA-00933andORA-00936, so persisted inbox/outbox messages are not recovered.Root cause and fix
The shared
DatabaseOperationBatchimplementation assumes that a provider can execute several result sets from one multi-statement command. Oracle does not support that command shape through ODP.NET.This change adds an internal provider hook without changing Wolverine's public API.
OracleMessageStoreuses that hook to configure each existingIDatabaseOperationindependently, execute the statements in order within one Oracle transaction, invoke its result callback, and then keep the existing post-processing behavior.The Oracle executor also:
@markers to:NUMBER(1)-compatible valuesGuidvalues to OracleRAWOther database providers continue through the existing batching path.
Fixes #3614.
Validation
dotnet test src/Persistence/Oracle/OracleTests/OracleTests.csproj --no-restore --filter "FullyQualifiedName~oracle_durability_command_translation"Passed 3 tests on
net9.0and 3 tests onnet10.0.This is opened as a draft to allow discussion of the provider-specific execution hook and test coverage.